package com.tianhai.db;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 磁盘管理器 - 负责页的持久化存储
 * 使用FileChannel进行文件操作，支持线程安全
 */
public class DiskManager {
    // 数据库文件路径
    private final String dbFilePath;
    
    // 文件通道
    private FileChannel fileChannel;
    
    // 随机访问文件
    private RandomAccessFile randomAccessFile;
    
    // 读写锁，保证线程安全
    private final ReadWriteLock rwLock;
    
    // 下一个可用的页ID (原子操作)
    private final AtomicInteger nextPageId;
    
    // 是否已关闭
    private volatile boolean closed;
    
    /**
     * 构造函数
     * @param dbFilePath 数据库文件路径
     * @throws IOException 文件操作异常
     */
    public DiskManager(String dbFilePath) throws IOException {
        this.dbFilePath = dbFilePath;
        this.rwLock = new ReentrantReadWriteLock();
        this.closed = false;
        
        // 初始化文件和通道
        initializeFile();
        
        // 计算下一个可用页ID
        this.nextPageId = new AtomicInteger(calculateNextPageId());
    }
    
    /**
     * 初始化数据库文件
     * @throws IOException 文件操作异常
     */
    private void initializeFile() throws IOException {
        Path path = Paths.get(dbFilePath);
        
        // 确保父目录存在
        if (path.getParent() != null) {
            Files.createDirectories(path.getParent());
        }
        
        // 打开文件（读写模式）
        this.randomAccessFile = new RandomAccessFile(dbFilePath, "rw");
        this.fileChannel = randomAccessFile.getChannel();
    }
    
    /**
     * 计算下一个可用的页ID
     * 基于文件大小计算
     * @return 下一个页ID
     * @throws IOException 文件操作异常
     */
    private int calculateNextPageId() throws IOException {
        long fileSize = fileChannel.size();
        int totalPages = (int) (fileSize / Page.PAGE_SIZE);
        return totalPages;
    }
    
    /**
     * 从文件读取指定页
     * @param pageId 页ID
     * @return 读取的Page对象
     * @throws IOException 文件操作异常
     */
    public Page readPage(int pageId) throws IOException {
        checkClosed();
        
        if (pageId < 0) {
            throw new IllegalArgumentException("页ID不能为负数: " + pageId);
        }
        
        rwLock.readLock().lock();
        try {
            // 计算页在文件中的偏移量
            long offset = (long) pageId * Page.PAGE_SIZE;
            
            // 检查文件大小
            if (offset >= fileChannel.size()) {
                throw new IllegalArgumentException(
                    String.format("页ID %d 超出文件范围 (文件大小: %d bytes)", 
                    pageId, fileChannel.size())
                );
            }
            
            // 创建缓冲区并读取数据
            ByteBuffer buffer = ByteBuffer.allocate(Page.PAGE_SIZE);
            int bytesRead = fileChannel.read(buffer, offset);
            
            if (bytesRead != Page.PAGE_SIZE) {
                throw new IOException(
                    String.format("读取页 %d 失败: 期望读取 %d 字节，实际读取 %d 字节", 
                    pageId, Page.PAGE_SIZE, bytesRead)
                );
            }
            
            // 创建Page对象
            buffer.flip();
            byte[] pageData = new byte[Page.PAGE_SIZE];
            buffer.get(pageData);
            
            // 对于B+树等直接使用原始页数据的场景，支持不解析头部
            return new Page(pageId, pageData, true);  // 默认解析头部，保持兼容性
            
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    /**
     * 将页写入文件
     * @param page 要写入的Page对象
     * @throws IOException 文件操作异常
     */
    public void writePage(Page page) throws IOException {
        checkClosed();
        
        if (page == null) {
            throw new IllegalArgumentException("页对象不能为null");
        }
        
        int pageId = page.getPageId();
        if (pageId < 0) {
            throw new IllegalArgumentException("页ID不能为负数: " + pageId);
        }
        
        rwLock.writeLock().lock();
        try {
            // 计算页在文件中的偏移量
            long offset = (long) pageId * Page.PAGE_SIZE;
            
            // 获取页数据并写入
            byte[] pageData = page.getData();
            ByteBuffer buffer = ByteBuffer.wrap(pageData);
            
            int bytesWritten = fileChannel.write(buffer, offset);
            
            if (bytesWritten != Page.PAGE_SIZE) {
                throw new IOException(
                    String.format("写入页 %d 失败: 期望写入 %d 字节，实际写入 %d 字节", 
                    pageId, Page.PAGE_SIZE, bytesWritten)
                );
            }
            
            // 强制刷新到磁盘
            fileChannel.force(true);
            
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    /**
     * 创建新页并分配pageId
     * @return 新创建的Page对象
     * @throws IOException 文件操作异常
     */
    public Page allocateNewPage() throws IOException {
        checkClosed();
        
        // 原子性地获取并递增页ID
        int newPageId = nextPageId.getAndIncrement();
        
        rwLock.writeLock().lock();
        try {
            // 创建新页
            Page newPage = new Page(newPageId);
            
            // 计算新页在文件中的位置
            long offset = (long) newPageId * Page.PAGE_SIZE;
            
            // 扩展文件大小（如果需要）
            long requiredSize = offset + Page.PAGE_SIZE;
            if (fileChannel.size() < requiredSize) {
                // 使用position方法扩展文件
                fileChannel.position(requiredSize - 1);
                fileChannel.write(ByteBuffer.wrap(new byte[]{0}));
            }
            
            // 注意：不写入Page的头部数据，只是分配空间
            // 实际数据由调用者通过writePage写入
            // 不需要写入任何数据，只是确保文件空间被分配
            
            return newPage;
            
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    /**
     * 获取当前数据库的总页数
     * @return 总页数
     * @throws IOException 文件操作异常
     */
    public int getTotalPages() throws IOException {
        checkClosed();
        
        rwLock.readLock().lock();
        try {
            long fileSize = fileChannel.size();
            return (int) (fileSize / Page.PAGE_SIZE);
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    /**
     * 从文件读取指定页（原始字节数据，不解析页头）
     * @param pageId 页ID
     * @return 读取的原始字节数组
     * @throws IOException 文件操作异常
     */
    public byte[] rawReadPage(int pageId) throws IOException {
        checkClosed();
        
        if (pageId < 0) {
            throw new IllegalArgumentException("页ID不能为负数: " + pageId);
        }
        
        rwLock.readLock().lock();
        try {
            // 计算页在文件中的偏移量
            long offset = (long) pageId * Page.PAGE_SIZE;
            
            // 检查文件大小
            if (offset >= fileChannel.size()) {
                throw new IllegalArgumentException(
                    String.format("页ID %d 超出文件范围 (文件大小: %d bytes)", 
                    pageId, fileChannel.size())
                );
            }
            
            // 创建缓冲区并读取数据
            ByteBuffer buffer = ByteBuffer.allocate(Page.PAGE_SIZE);
            int bytesRead = fileChannel.read(buffer, offset);
            
            if (bytesRead != Page.PAGE_SIZE) {
                throw new IOException(
                    String.format("读取页 %d 失败: 期望读取 %d 字节，实际读取 %d 字节", 
                    pageId, Page.PAGE_SIZE, bytesRead)
                );
            }
            
            buffer.flip();
            byte[] pageData = new byte[Page.PAGE_SIZE];
            buffer.get(pageData);
            
            return pageData;
            
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    /**
     * 获取数据库文件路径
     * @return 文件路径
     */
    public String getDbFilePath() {
        return dbFilePath;
    }
    
    /**
     * 获取下一个可用的页ID
     * @return 下一个页ID
     */
    public int getNextPageId() {
        return nextPageId.get();
    }
    
    /**
     * 刷新所有缓冲数据到磁盘
     * @throws IOException 文件操作异常
     */
    public void flush() throws IOException {
        checkClosed();
        
        rwLock.writeLock().lock();
        try {
            fileChannel.force(true);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    /**
     * 关闭磁盘管理器
     * @throws IOException 文件操作异常
     */
    public void close() throws IOException {
        if (closed) {
            return;
        }
        
        rwLock.writeLock().lock();
        try {
            if (fileChannel != null && fileChannel.isOpen()) {
                fileChannel.force(true);
                fileChannel.close();
            }
            
            if (randomAccessFile != null) {
                randomAccessFile.close();
            }
            
            closed = true;
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    /**
     * 检查管理器是否已关闭
     * @throws IllegalStateException 如果已关闭
     */
    private void checkClosed() {
        if (closed) {
            throw new IllegalStateException("DiskManager已关闭");
        }
    }
    
    /**
     * 检查管理器是否已关闭
     * @return true表示已关闭
     */
    public boolean isClosed() {
        return closed;
    }
    
    @Override
    public String toString() {
        try {
            return String.format("DiskManager[file=%s, totalPages=%d, nextPageId=%d, closed=%b]",
                dbFilePath, 
                closed ? -1 : getTotalPages(), 
                nextPageId.get(), 
                closed);
        } catch (IOException e) {
            return String.format("DiskManager[file=%s, error=%s]", dbFilePath, e.getMessage());
        }
    }
}
